/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.specific.sge.status;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.regex.Pattern;

import jp.aist.gtrc.plus.reserve.ReserveException;
import jp.aist.gtrc.plus.scheduler.specific.sge.status.SGEComplexEntry.RelationOpe;
import jp.aist.gtrc.plus.scheduler.specific.sge.status.SGEComplexEntry.ValType;
import jp.aist.gtrc.plus.scheduler.sys.SchedulerException;
import jp.aist.gtrc.plus.scheduler.util.CmdUtil;
import jp.aist.gtrc.plus.scheduler.util.LastTimeChecker;
import jp.aist.gtrc.plus.scheduler.util.TimeUtil;
import jp.aist.gtrc.plus.scheduler.util.TextUtil;

public class SGEComplex {

	private static final HashMap<String, SGEComplexEntry> nameMap = new HashMap<String, SGEComplexEntry>();
	private static final HashMap<String, SGEComplexEntry> shortcutMap = new HashMap<String, SGEComplexEntry>();
	private static final int RELOAD_PERIOD = 60;	// [sec]
	private static final LastTimeChecker lastCheker = new LastTimeChecker(RELOAD_PERIOD);

	private static synchronized void reloadConfig() {

		if (lastCheker.mustUpdate() == false)
			return;

		LinkedList<String> stdout;
		try {
			stdout = CmdUtil.runCommand(new String[]{
					"qconf", "-sc"});
		} catch (ReserveException e) {
			// ignore
			return;
		}

		nameMap.clear();
		shortcutMap.clear();

		for (String line : stdout) {
			if (line.startsWith("#")) {
				continue;
			}
			String[] args = line.split("\\s+");
			if (args.length != 8) {
				continue;
			}
			SGEComplexEntry entry = new SGEComplexEntry(
					args[0],
					args[1],
					ValType.getValType(args[2]),
					RelationOpe.getRelationOpe(args[3]),
					makeBoolean(args[4]),
					makeBoolean(args[5]),
					args[6],
					Integer.parseInt(args[7]));
			nameMap.put(entry.getName(), entry);
			shortcutMap.put(entry.getShortcutName(), entry);
		}

	}

	private static boolean compare(RelationOpe ope, double cmp) {

		switch (ope) {
		case EQ:
			return (cmp == 0);
		case GE:
			return (cmp >= 0);
		case GT:
			return (cmp > 0);
		case LT:
			return (cmp < 0);
		case LE:
			return (cmp <= 0);
		case NE:
			return (cmp != 0);
		default:
			assert(false);
			return false;
		}

	}

	private static boolean compareInteger(RelationOpe ope, String value1, String value2) {

		/*
		 * NOTE: SGE treats integer as double internally, ex "2" as "2.0000".
		 * We use compareDouble() here.
		 */
		return compareDouble(ope, value1, value2);

	}

	private static boolean compareString(RelationOpe ope, String v1, String v2) {

		return compare(ope, v1.compareTo(v2));

	}

	public static final int makeTime(String value) throws SchedulerException {

		try {
			return TimeUtil.hhmmssToSec(value);
		} catch (NumberFormatException e) {
			throw new SchedulerException(e.getMessage());
		}

	}

	private static boolean compareTime(RelationOpe ope, String value1, String value2) {

		try {
			int v1 = makeTime(value1);
			int v2 = makeTime(value2);
			return compare(ope, v1 - v2);
		} catch (SchedulerException e) {
			return false;
		}

	}

	public static final double makeMemory(String value) throws SchedulerException {

		try {
			return TextUtil.calcSize(value);
		} catch (NumberFormatException e) {
			throw new SchedulerException(e.getMessage());
		}

	}

	private static boolean compareMemory(RelationOpe ope, String value1, String value2) {

		try {
			return compare(ope, makeMemory(value1) - makeMemory(value2));
		} catch (SchedulerException e) {
			return false;
		}

	}

	public static final boolean makeBoolean(String value) {

		// NOTE: SGE actually support "YES"/"NO"
		return (value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true"));

	}

	private static boolean compareBoolean(RelationOpe ope, String value1, String value2) {

		boolean v1 = makeBoolean(value1);
		boolean v2 = makeBoolean(value2);
		switch (ope) {
		case EQ:
			return (v1 == v2);
		case NE:
			return (v1 != v2);
		default:
			assert(false);
			return false;
		}

	}

	private static boolean compareCString(RelationOpe ope, String value1, String value2) {

		return compare(ope, value1.compareToIgnoreCase(value2));

	}

	private static boolean compareHost(RelationOpe ope, String value1, String value2) {

		return compareCString(ope, value1, value2);

	}

	private static boolean compareDouble(RelationOpe ope, String value1, String value2) {

		try {
			double v1 = Double.parseDouble(value1);
			double v2 = Double.parseDouble(value2);
			return compare(ope, v1 - v2);
		} catch (NumberFormatException e) {
			return false;
		}

	}

	public static final boolean compareREString(RelationOpe ope, String wildEx, String value2) {

		/*
		 * regex: Regular expression using shell wild card.
		 * 			ex: "lx26-*", "*-sparc" etc
		 * 			Replace "." -> "\.", "*" -> ".*" before RE matching test.
		 * 			See complex(5).
		 * value2: normal text to be checked.
		 * 			ex: "lx26-x86", "sol-sparc" etc
		 */
		String regex = wildEx.replaceAll("\\.", "\\.");
		regex = regex.replaceAll("\\*", ".*");
		Pattern pattern = Pattern.compile(regex);
		boolean isMatch = pattern.matcher(value2).matches();
		switch (ope) {
		case EQ:
			return (isMatch == true);
		case NE:
			return (isMatch == false);
		default:
			assert(false);
			return false;
		}

	}

	static SGEComplexEntry getComplex(String name) {

		reloadConfig();

		/*
		 * nameMap key is "arch", "mem_free" etc
		 * shortcutMap key is "a", "mf" etc
		 */
		SGEComplexEntry entry = nameMap.get(name);
		if (entry != null) {
			return entry;
		} else {
			return shortcutMap.get(name);
		}

	}

	public static boolean isResourceMatch(String name, String v1, String v2) {

		SGEComplexEntry entry = getComplex(name);
		if (entry == null) {
			return false;
		}
		if (entry.isRequestable() == false) {
			return false;
		}

		RelationOpe ope = entry.getRelationOpe();
		String value1 = TextUtil.toNonNull(v1);
		String value2 = TextUtil.toNonNull(v2);

		switch (entry.getValType()) {
		case INT:
			return compareInteger(ope, value1, value2);
		case STRING:
			return compareString(ope, value1, value2);
		case TIME:
			return compareTime(ope, value1, value2);
		case MEMORY:
			return compareMemory(ope, value1, value2);
		case BOOL:
			return compareBoolean(ope, value1, value2);
		case CSTRING:
			return compareCString(ope, value1, value2);
		case HOST:
			return compareHost(ope, value1, value2);
		case DOUBLE:
			return compareDouble(ope, value1, value2);
		case RESTRING:
			return compareREString(ope, value1, value2);
		default:
			assert(false);
			return false;
		}

	}

}
